This notebook is based on one put together by Mark Krumholz and has been modified to suit the purposes of this course, including expansion/modification of explanations and additional exercises. The original can be found at https://sites.google.com/a/ucsc.edu/krumholz/teaching-and-courses/ast119_w15/class-6.

Names: [Insert Your Names Here]

Lab 8 - Plotting in 2D

Lab 8 Contents

  1. Plotting 2D Data
    • Generating 2D data
    • Contour plots
    • Raster, or "heatmap", plots
  2. Multipanel Plots

In [ ]:
from numpy import *
import matplotlib.pyplot as plt
%matplotlib inline

1. Plotting 2D Data

1.1 Generating 2D data

Thus far we have dealt mostly with 1D data: something that can be described as a function y(x). However, we also often have 2D data, which is represented as a function of two variables z(x,y). Matplotlib also provides mechanisms to visually represent data of this sort. To start with, let's make some 2D data that we can play with. Here's the first step:


In [ ]:
x=arange(-2*pi,2*pi,0.01)

In [ ]:
y=arange(-2*pi,2*pi,0.01)

In [ ]:
xx, yy = meshgrid(x, y, indexing='ij')

In [ ]:
# the .shape attribute is very useful with 2D arrays
x.shape

In [ ]:
xx.shape

The first two lines should look familiar: they just create two arrays, x and y, that go from -2 pi to 2 pi with a spacing of 0.01. The third line invokes a function you probably haven't seen before, called meshgrid, which is part of the numpy library. The meshgrid function does something very useful. In this case we've passed it two one-dimensional arrays, representing x and y coordinates. We can think of these two arrays as defining a 2D grid of points, with formed by combining all the possible x values with all the possible y values. The meshgrid function gives us back a pair of two-dimensional arrays, xx and yy, that represent the x and y coordinates of these mesh points. Specifically, xx[0,:] = x[0], xx[1,:] = x[1], xx[2,:] = x[2], ..., and similarly yy[:,0] = y[0], yy[:,1] = y[1], yy[:,2] = y[2], and so forth. The keyword indexing='ij' specifies that the x coordinate goes with the first dimension of the output arrays, and the y coordinate with the second. Finally, note that, though we've used it in 2D, the meshgrid command will work for an arbitrary number of dimensions.

Next, let's make some 2D functions from this data. The utility of meshgrid becomes clear as soon as we want to do this:


In [ ]:
r = sqrt(xx**2 + yy**2)
r.shape

In [ ]:
phi = arccos(x/r)

In [ ]:
z1 = sin(2*r) / r

In [ ]:
z2 = z1 * (1 + 0.5*cos(4*phi))

The first two lines compute the r and phi coordinates in a polar coordinate system. The second two lines define two functions that we'll play with. The function z1 is just the sinc function in 2D, where we take sinc(r) instead of sinc(x). We've put a factor of 2 inside the sin to change the period from 2 pi to pi. The second function z2 just takes z1 and multiplies it by a factor that varies between 1.5 and 0.5 depending on the angle phi, with a period of pi/2 radians in the phi direction.

1.2 Contour plots

How can we represent data like this? One simple way is through a contour plot. The archetypical contour plot is a topographic map: it shows curves of constant height, or more generally equal z value, as a function of (x,y) position. Matplotlib produces contour plots using the contour() command. Basic usage is simple: contour(xx, yy, z). The first two arguments are the x and y coordinates, and these can be either 1D arrays whose size matches the corresponding size of the z array, or 2D arrays whose shape matches that of the z array. Let's see what happens when we do this:


In [ ]:
plt.contour(xx, yy, z1)

The output looks like this:

This is a contour plot of the data. One minor annoyance is that the spacing on the x and y axes isn't equal, so things look stretched. We can fix that by changing the aspect ratio of the axes. To do that, we use the commands


In [ ]:
plt.gca().set_aspect('equal')
plt.contour(xx, yy, z1)

Let's break down these commands so we can understand what they're doing. First, gca() stands for get current axes. This function lets us grab the axes we're currently plotting to -- something that will become important when we get to multiple axes in a few minutes. Then the command set_aspect('equal'), as applied to these axes, tells pyplot that we want the aspect ratio of the plot to be such that the spacing of points on the axes is equal, even if that means not filling the whole plot window.

The result of this procedure should look something like this:

For this to be more useful in a quantitative sense, it's helpful to have some control over the values at which contours are placed. Fortunately, this is easy to do with an optional additional argument to the contour function. After x, y, and z, one can pass in a fourth argument describing the contours. This can be either a single number, which just specifies how many contours to use, or an array giving the exact values to use for the contours. For example, for our sinc function we know that the maximum is 2.0, and the minimum is -0.43 (you can show this analytically, or just check by doing amax(z1) and amin(z1)), so we can choose contours to go from -0.4 to 1.0 in a spacing of 0.1.


In [ ]:
plt.gca().set_aspect('equal')
plt.contour(xx, yy, z1, arange(-0.4, 2.0, 0.1))

We can also label the contours using the clabel() command. Let's remake this plot using some labelled contours, placing the contours starting at -0.4 and going in intervals of 0.4.


In [ ]:
plt.gca().set_aspect('equal')
cs = plt.contour(xx, yy, z1, arange(-0.4, 2.0, 0.4))
plt.clabel(cs)

Note that, to add labels, we assign a variable to the output of the contour function, and then pass that variable to the clabel function. That way we can have multiple sets of contours at once, and label them separately, since each will be referred to by its own set of variables.

Here is the result:

As usual, there are numerous options to control every aspect of the contours, including line styles and thicknesses, colors, label placement, font, etc. For details on contour, see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.contour. For details on clabel, see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.clabel.

We can also make filled contours, which may be a little easier to view. Filled contours are made by the contourf() command, which acts almost exactly like contour. To make filled contours for our function z2, we can do


In [ ]:
plt.gca().set_aspect('equal')
plt.contourf(xx, yy, z2, arange(-0.7, 3.21, 0.5))

Here is the output:

Note that the contourf command doesn't draw contour lines, it just fills the space in between. If you want to label the contours, you can do that by calling contour after calling contourf. For example,


In [ ]:
plt.gca().set_aspect('equal')
plt.contourf(xx, yy, z2, arange(-0.7, 3.21, 0.5))
cs=plt.contour(xx, yy, z2, arange(-0.7,3.21,0.5), colors='white')
plt.clabel(cs)

Note that the colors option to the contour command specifies that all the contours should be drawn in white. The default colors will just blend in with the color fillings already used, so we won't be able to see the lines. Also, by default contours have the nice feature that, for contours that are all the same color, the negative ones will be drawn dashed. Here's the result:

1.3 Raster, or "heatmap", plots

A second useful way of representing quantitative information is with raster plots, also sometimes called heatmap or colormap plots. In such a plot, we assign every z value to a color, so that the color at a given (x,y) position is determined by the value of z there. It's something like a filled contour plot, but with colors assigned continuously rather than in discrete blocks.

The pyplot tool for making raster plots is called imshow(), short for image-show. Basic usage is as follows:


In [ ]:
plt.imshow(z2, aspect='equal', origin='lower', extent=(-2*pi, 2*pi, -2*pi, 2*pi))

Notice that the syntax of imshow() is a little different than contour, and its operation is a bit different.

For imshow, you don't give arrays of x and y values. This is for two reasons. First, imshow is often used as a way of displaying images, in which case there aren't really x and y coordinates, just pixels values. Second, for contour, you are allowed to have x and y points that aren't evenly spaced, whereas for imshow() you aren't. Thus you don't need to specify every x and y point, just the minimum and maximum.

The mechanism for specifying the minimum and maximum is the extent keyword, which you set equal to a tuple of 4 numbers (xmin, xmax, ymin, ymax). If you don't specify the extent keyword, xmin and ymin are taken to be 0, and xmax and ymax to the number of elements in the data in the x and y directions.

The aspect ratio can be set in the imshow command itself, as opposed to through modifying the axes. This is done via the keyword aspect. Setting it to equal forces equal spacing on the two axes.

Finally, the origin keyword specifies where (0,0) is placed. This is another legacy of imshow also being used for image display. When displaying images, the normal convention is that pixel (0,0) is in the top left corner, (0,1) is right below it, (0,2) is right below that, etc. This is the default for imshow(). To display things as we normally plot data, where (0,0) is the origin, (0,1) is above it, etc., we specify origin='lower', which says to place the origin in the lower left rather than upper left corner.

Here's the result:

This is useful, but even more useful is if we can add some information on what the colors mean. The tool for doing this is a color bar, which can be added via the colorbar() command:


In [ ]:
plt.imshow(z2, aspect='equal', origin='lower', extent=(-2*pi, 2*pi, -2*pi, 2*pi))
plt.colorbar()

The output looks like this:

It is also possible to control the color scale using the vmin and vmax keywords in the imshow command. These allow one to specify the minimum and maximum values for the color mapping by hand; by default the minimum used is the minimum of the input data, and the maximum is the maximum of the input data. Here's an example:


In [ ]:
plt.imshow(z2, aspect='equal', origin='lower', extent=(-2*pi,2*pi,-2*pi,2*pi), vmin=-1, vmax=3)
plt.colorbar()

Note that although the underlying data are the same, changing vmin and vmax has the effect of highlighting different aspects of the data. Choice of colorbars and colorscales therefore has aesthetic and, in some cases, ethical effects (for example, you could obscure something that you didn't want a viewer to see with a calculated colorbar choice).

More information on imshow can be found at http://matplotlib.org/users/image_tutorial.html and http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow.

Exercise 1


Write a function that creates a 2D array where the value of each pixel is the distance from some specified central pixel to that pixel. The function should assume an array size of 100x100, but should allow for specification of larger arrays. It should also assume that the centermost pixel of the array is the "central pixel" from which to calculate distances, unless otherwise specified. Once you have it working, create contour plots with labeled contour levels AND raster plots with colorbars for each of the following:
a) a 100x100 array with the central pixel as the reference pixel
b) a 200x200 array with the lower left corner as the reference pixel
c) a 100x300 array with pixel (75,200) as the reference pixel

2. Multi-Panel Plots

Thus far we have been dealing with a single figure, containing a single set of axes. However, it is often desirable to work with multiple plots at once, either in separate figures, or inside a single figure. Matplotlib makes it possible for us to do this. To understand how this works, we need to start with some basic terminology and concepts. The highest level object that a user of pyplot usually deals with is a figure. One can think of a figure as representing a single plotting window, or, if we're writing output to files, a single file.

At the next level down, most things that we can plot inside a figure will go into axes. Axes are exactly what they sound like: a pair of x and y axes, which are characterized by having some range, as well as auxiliary data like tick marks, axis labels, titles, legends, etc. A figure can contain multiple axes, in which case multiple things will be drawn inside the same plotting window. Axes also contain information about their position within a figure. Figures and axes are both examples of graphics containers: they are things into which graphical elements can be placed.

At the next level down are things like lines, filled polygons (filled regions between lines, for example), text, etc. These are known as graphics primitives. Collectively, primitives and containers are known as Artists. They are the basic graphical elements out of which plots are built. Commands like plot, fill_between, etc., produce primitives and attach them to containers.

One figure and one axis are active at any given time. The active figure and axis are the ones into which the lines or other graphics primitives created by commands like plot will be placed. It is possible to place graphics in other figures and axes than the active one by manually specifying where they should go; the active set simply gives the default location. Figures and the figure command

The easiest case is working with multiple figures. These can be created using the plt.figure() command as follows


In [ ]:
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(10,3), sharey=True)
f.suptitle('Three plots')
im1 = ax1.set_aspect('equal')
ax1.contourf(xx, yy, z2, arange(-0.7, 3.21, 0.5))
cs=ax1.contour(xx, yy, z2, arange(-0.7,3.21,0.5), colors='white')
ax1.clabel(cs)
ax1.set_xlabel("distance from center")
ax1.set_ylabel("distance from center")
im2 = ax2.imshow(z2, aspect='equal', origin='lower', extent=(-2*pi, 2*pi, -2*pi, 2*pi))
f.colorbar(im2, ax=ax2)
im3 = ax3.imshow(z2, aspect='equal', origin='lower', extent=(-2*pi,2*pi,-2*pi,2*pi), vmin=-1, vmax=3)
f.colorbar(im3, ax=ax3)
plt.tight_layout()

Exercise 2


Play around with the example above by changing various things unitl you understand everything its doing. Then, use what you learned to create a 2x2 set of raster plots generated by your radial array function from exercise 1. Each should have a colorbar, axis labels and a title, and the entire plot should have a title at the top.


In [ ]:


In [1]:
from IPython.core.display import HTML
def css_styling():
    styles = open("../custom.css", "r").read()
    return HTML(styles)
css_styling()


Out[1]: